﻿Imports System.Activities
Imports System.Activities.Validation
Imports System.Collections.ObjectModel
Imports System.Xaml
Imports System.IO
Imports System.Reflection.Emit
Imports System.Activities.XamlIntegration

''' <remarks>http://blogs.msdn.com/b/tilovell/archive/2011/02/19/secrets-of-the-xaml-build-task.aspx</remarks>
Public Class XamlActivityCompiler

    Public Shared Function Compile(ByVal dllPath As String, ByVal xamlPath As String, ByVal assmeblyNames As String()) As String
        Dim schemaContext As New XamlSchemaContext

        Dim xamlLoadedObject As Object = Nothing
        Using XamlReader As New XamlXmlReader(xamlPath)
            Dim builderReader = ActivityXamlServices.CreateBuilderReader(XamlReader, schemaContext)
            xamlLoadedObject = XamlServices.Load(builderReader)
            builderReader.Close()
        End Using
        Dim actBuilder = TryCast(xamlLoadedObject, ActivityBuilder)

        Dim ns As String = dllPath
        Dim ab = AppDomain.CurrentDomain.DefineDynamicAssembly(
                    New Reflection.AssemblyName(ns) With {
                        .Version = New Version(1, 0, 0, 0)
                    }, AssemblyBuilderAccess.RunAndSave)
        Dim abFake = AppDomain.CurrentDomain.DefineDynamicAssembly(
                    New Reflection.AssemblyName(ns) With {
                        .Version = New Version(1, 0, 0, 0)
                    }, AssemblyBuilderAccess.Run)

        Dim mb = ab.DefineDynamicModule(ns, ns)
        Dim mbFake = abFake.DefineDynamicModule(ns)

        Dim newClass = BuildType(mb, actBuilder, False)
        Dim createdType = newClass.CreateType()

        Dim newClassFake = BuildType(mbFake, actBuilder, True)
        Dim fakeType = newClassFake.CreateType()

        Dim magicXaml = GenerateXAML(actBuilder, fakeType)
        Dim stringStream = New MemoryStream(Text.Encoding.Unicode.GetBytes(magicXaml))

        Dim xamlResourceName = createdType.FullName + ".xaml"
        mb.DefineManifestResource(xamlResourceName, stringStream, Reflection.ResourceAttributes.Public)

        ab.Save(ns)
        Return ns

    End Function

    Public Shared Function BuildType(ByVal modBuilder As ModuleBuilder, ByVal actBuilder As ActivityBuilder, ByVal fakeType As Boolean) As TypeBuilder
        Dim baseType = GetType(Activity)
        If fakeType Then baseType = GetType(Object)

        Dim newType = modBuilder.DefineType(actBuilder.Name, Reflection.TypeAttributes.Public, baseType)

        Dim ctorAttributes = Reflection.MethodAttributes.Public Or Reflection.MethodAttributes.HideBySig Or Reflection.MethodAttributes.SpecialName Or Reflection.MethodAttributes.RTSpecialName
        Dim baseCtorSearchFlags = Reflection.BindingFlags.Instance Or Reflection.BindingFlags.Public Or Reflection.BindingFlags.NonPublic

        Dim ctor = newType.DefineConstructor(ctorAttributes, Reflection.CallingConventions.Standard, Type.EmptyTypes)
        Dim baseCtor = baseType.GetConstructor(baseCtorSearchFlags, Nothing, Type.EmptyTypes, Nothing)

        Dim ctorILGenerator = ctor.GetILGenerator()
        ctorILGenerator.Emit(OpCodes.Ldarg_0)
        ctorILGenerator.Emit(OpCodes.Call, baseCtor)
        If Not fakeType Then
            Dim initialize = GetType(XamlActivityInitializer).GetMethod("Initialize", Reflection.BindingFlags.Static Or Reflection.BindingFlags.Public)
            ctorILGenerator.Emit(OpCodes.Ldarg_0)
            ctorILGenerator.Emit(OpCodes.Call, initialize)
        End If
        ctorILGenerator.Emit(OpCodes.Ret)

        For Each prop In actBuilder.Properties
            DefineGetSetProperty(newType, prop.Name, prop.Type)
        Next

        If fakeType Then
            DefineGetSetProperty(newType, "Implementation", GetType(Activity))
            DefineLazyGetProperty(newType, "Constraints", GetType(Collection(Of Constraint)))
        End If
        Return newType

    End Function

    Private Shared Function GenerateXAML(ByVal builder As ActivityBuilder, ByVal fakeType As Type) As String

        Dim fakeInstance = Activator.CreateInstance(fakeType)

        For Each prop In builder.Properties
            Dim pi = fakeInstance.GetType.GetProperty(prop.Name)
            pi.SetValue(fakeInstance, prop.Value, Nothing)
        Next

        Dim implementationProperty = fakeType.GetProperty("Implementation")
        implementationProperty.SetValue(fakeInstance, builder.Implementation, Nothing)

        Dim constraintsProperty = fakeType.GetProperty("Constraints")
        Dim constraintsCollection = TryCast(constraintsProperty.GetValue(fakeInstance, Nothing), Collection(Of Constraint))
        For Each constr In builder.Constraints
            constraintsCollection.Add(constr)
        Next

        Dim readerSettings As New XamlObjectReaderSettings With {
            .AllowProtectedMembersOnRoot = True
        }
        Dim reader As New XamlObjectReader(fakeInstance, readerSettings)

        Dim sb As New Text.StringBuilder
        Dim textWriter As New StringWriter(sb)
        Dim writer As New XamlXmlWriter(textWriter, New XamlSchemaContext)

        XamlServices.Transform(reader, writer)
        writer.Flush()
        textWriter.Flush()

        Return sb.ToString

    End Function

    Private Shared Sub DefineGetSetProperty(ByVal newType As TypeBuilder, ByVal propertyName As String, ByVal propertyType As Type)

        Dim backingFIeld = newType.DefineField("_" + propertyName, propertyType, Reflection.FieldAttributes.Private)
        Dim accessorAttributes = Reflection.MethodAttributes.Public Or Reflection.MethodAttributes.HideBySig Or Reflection.MethodAttributes.SpecialName
        Dim prop = newType.DefineProperty(propertyName, Reflection.PropertyAttributes.None, propertyType, Nothing)

        Dim getter = newType.DefineMethod("get_" + propertyName, accessorAttributes)
        getter.SetReturnType(propertyType)
        getter.SetParameters(Type.EmptyTypes)

        Dim getterILGenerator = getter.GetILGenerator()
        getterILGenerator.Emit(OpCodes.Ldarg_0)
        getterILGenerator.Emit(OpCodes.Ldfld, backingFIeld)
        getterILGenerator.Emit(OpCodes.Ret)

        Dim setter = newType.DefineMethod("set_" + propertyName, accessorAttributes)
        setter.SetReturnType(Nothing)
        setter.SetParameters(New Type() {propertyType})

        Dim setterILGenerator = setter.GetILGenerator()
        setterILGenerator.Emit(OpCodes.Ldarg_0)
        setterILGenerator.Emit(OpCodes.Ldarg_1)
        setterILGenerator.Emit(OpCodes.Stfld, backingFIeld)
        setterILGenerator.Emit(OpCodes.Ret)

        prop.SetGetMethod(getter)
        prop.SetSetMethod(setter)

    End Sub

    Private Shared Sub DefineLazyGetProperty(ByVal newType As TypeBuilder, ByVal propertyName As String, ByVal propertyType As Type)

        Dim backingField = newType.DefineField("_" + propertyName, propertyType, Reflection.FieldAttributes.Private)
        Dim accessorAttributes = Reflection.MethodAttributes.Public Or Reflection.MethodAttributes.HideBySig Or Reflection.MethodAttributes.SpecialName
        Dim constraintsProperty = newType.DefineProperty(propertyName, Reflection.PropertyAttributes.None, propertyType, Nothing)

        Dim getter = newType.DefineMethod("get_" + propertyName, accessorAttributes)
        getter.SetReturnType(propertyType)
        getter.SetParameters(Type.EmptyTypes)

        Dim getterILGenerator = getter.GetILGenerator()
        Dim doneInit = getterILGenerator.DefineLabel()
        getterILGenerator.Emit(OpCodes.Ldarg_0)
        getterILGenerator.Emit(OpCodes.Ldfld, backingField)
        getterILGenerator.Emit(OpCodes.Brtrue, doneInit)

        Dim defaultConstructor = propertyType.GetConstructor(Type.EmptyTypes)
        getterILGenerator.Emit(OpCodes.Ldarg_0)
        getterILGenerator.Emit(OpCodes.Newobj, defaultConstructor)
        getterILGenerator.Emit(OpCodes.Stfld, backingField)

        getterILGenerator.MarkLabel(doneInit)
        getterILGenerator.Emit(OpCodes.Ldarg_0)
        getterILGenerator.Emit(OpCodes.Ldfld, backingField)
        getterILGenerator.Emit(OpCodes.Ret)

        constraintsProperty.SetGetMethod(getter)

    End Sub

End Class
